/*
 *  Copyright (C) 2004 Cidero, Inc.
 *
 *  Permission is hereby granted to any person obtaining a copy of 
 *  this software to use, copy, modify, merge, publish, and distribute
 *  the software for any non-commercial purpose, subject to the
 *  following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY IN CONNECTION WITH THE SOFTWARE.
 * 
 *  File: $RCSfile: AsyncCommandThread.java,v $
 *
 */

package com.cidero.bridge.prismiq;

import java.io.*;
import java.net.*;
import java.util.logging.Logger;

import com.cidero.util.SynchronizedQueue;
import com.cidero.util.AsyncCommand;

/**
 * Thread to communicate with Prismiq asynchronously (avoid hanging
 * main thread and allow for command timeout detection)
 */
public class AsyncCommandThread implements Runnable
{
  private static Logger logger = 
    Logger.getLogger("com.cidero.bridge.prismiq");

  private final static int RESPONSE_QUEUE_SIZE = 50; 

  PrismiqMediaRenderer   mediaRenderer;
  Socket                 socket = null;
  PrismiqAgentReadThread readThread = null;
  SynchronizedQueue      responseQueue;
  int                    seqNum = 0;
  
  /** 
   * Constructor
   */
  public AsyncCommandThread( PrismiqMediaRenderer mediaRenderer )
  {
    this.mediaRenderer = mediaRenderer;

    connect();
  }
  
  private Thread asyncCommThread = null;  // for clean shutdown via stop()
  
  public void start()
  {
    asyncCommThread = new Thread( this );
    asyncCommThread.start();
  }
  
  public void stop()
  {
    asyncCommThread = null;
  }
  
  /**
   * Connect to primiq agent running on port 2253
   *
   * Separate asynchronous reader thread is created to read back 
   * responses from PRISM device for more robust operation.
   * (avoid problems with blocking read hanging main thread if 
   * something goes wrong)
   */
  public void connect()
  {
    logger.info("Connecting to Prismiq Agent at " 
                + mediaRenderer.getIPAddr() + ":" + mediaRenderer.getPort() );

    if( socket == null )
    {
      try
      {
        socket = new Socket( mediaRenderer.getIPAddr(),
                             mediaRenderer.getPort() );

        if( readThread != null )  // Stop existing async read thread
          readThread.stop();

        responseQueue = new SynchronizedQueue( RESPONSE_QUEUE_SIZE );

        PrismiqAgentReadThread readThread = 
          new PrismiqAgentReadThread( mediaRenderer, socket, responseQueue );

        readThread.start();  // start async read thread
      }
      catch( Exception e )
      {
        logger.info( "Error connecting to Prismiq Agent on port " +
                     mediaRenderer.getPort() );
        socket = null;
        return;
      }
    }
    
    logger.info("Connected ok");
  }

  public void run()
  {
    logger.fine("AsyncCommandThread: Running...");

    Thread thisThread = Thread.currentThread();

    AsyncCommand asyncCmd = mediaRenderer.getAsyncCommand();

    while( asyncCommThread == thisThread )
    {
      //
      // Get cmd from parent thread. Timeout after 4 sec and check 
      // for thread shutdown. Get audiotron status every 5 sec.
      //
      String cmd = (String)asyncCmd.receiveRequest( 5000 );
      
      if( cmd != null )
      {
        String response = sendCmd( cmd, 5000 );

        if( response != null )
        {
          if( ! asyncCmd.sendResponse( response, 4000 ) )
            logger.fine( "AsyncCommThread: Error forwarding response: " );
        }
      }
      else
      {
        logger.finest( "AsyncCommThread: Timeout waiting for cmd: " );        
      }
    }

    logger.fine( "async thread shutting down... " );        
  }

  /**
   * Send a message to the prismiq socket. Messages are preceded by an
   * id string which is included as part of the response. This allows
   * the sender to check for errors due to missed responses, etc...
   *
   * @param   Command string
   * @return  response string or null if exception occurred during HTTP
   *          session 
   *
   */ 
  public String sendCmd( String cmd, int timeout )
  {
    if( socket == null )
      connect();

    if( socket == null )
    {
      System.out.println("sendMsg: Couldn't connect");
      return null;
    }
      
    seqNum = ((seqNum+1) % 100);
    if( seqNum == 0 )  // Zero reserved by Prismiq
      seqNum++;
    
    try 
    {
      OutputStream out = socket.getOutputStream();
      PrintStream pout = new PrintStream( out );
      pout.print( "[" + seqNum + "] " +  cmd );
      pout.flush();
    }
    catch( Exception e )
    {
      System.out.println( e );
      return null;
    }
    
    //System.out.print( "Sent msg: " + cmd );

    //
    // Wait for response with matching seqNum. If none found after timeout
    // period, punt
    //
    String response;
    
    while( (response = (String)responseQueue.get(timeout)) != null )
    {
      //System.out.println("response from queue: " + response );

      // Extract sequence number from response
      int seqStartIndex = response.indexOf('[');
      int seqEndIndex = response.indexOf(']');
      if( (seqStartIndex >= 0) && (seqEndIndex >= 0) )
      {
        String seqString = response.substring( seqStartIndex+1, seqEndIndex );
        int responseSeqNum = Integer.parseInt( seqString );
        if( responseSeqNum == seqNum )
        {
          logger.finer("Found response with matching seqNum");
          break;
        }
        else
        {
          logger.warning("Response seq " + responseSeqNum +
                         " doesn't match cmdSeq " + seqNum +
                         " response = " + response );
        }
      }
      else
      {
        logger.warning("Missing seqNum field in response" + 
                       seqStartIndex + " " + seqEndIndex );
      }
    }

    if( response == null )
    {
      logger.warning("Timeout waiting for matching response to cmd '" + 
                     cmd + "'" );
      return null;
    }

    // Return the whole response (includes seq number) 
    return response;
  }
  
}
